#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
飞书多维表格API客户端
实现OAuth授权、token管理和多维表格操作功能
"""

import json
import time
import webbrowser
import urllib.parse
from typing import Dict, List, Optional, Any
import requests
import os
from http.server import HTTPServer, BaseHTTPRequestHandler
import threading
import logging

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class TokenStorage:
    """Token存储管理类"""
    
    def __init__(self, storage_file: str = "feishu_tokens.json"):
        self.storage_file = storage_file
    
    def save_tokens(self, tokens: Dict[str, Any]):
        """保存token到本地文件"""
        try:
            # 添加时间戳
            tokens['saved_at'] = time.time()
            with open(self.storage_file, 'w', encoding='utf-8') as f:
                json.dump(tokens, f, indent=2, ensure_ascii=False)
            logger.info(f"Tokens saved to {self.storage_file}")
        except Exception as e:
            logger.error(f"Failed to save tokens: {e}")
    
    def load_tokens(self) -> Optional[Dict[str, Any]]:
        """从本地文件加载token"""
        try:
            if os.path.exists(self.storage_file):
                with open(self.storage_file, 'r', encoding='utf-8') as f:
                    tokens = json.load(f)
                logger.info(f"Tokens loaded from {self.storage_file}")
                return tokens
        except Exception as e:
            logger.error(f"Failed to load tokens: {e}")
        return None
    
    def is_token_expired(self, tokens: Dict[str, Any], buffer_seconds: int = 300) -> bool:
        """检查token是否即将过期（默认提前5分钟刷新）"""
        if not tokens or 'saved_at' not in tokens or 'expires_in' not in tokens:
            return True
        
        saved_at = tokens['saved_at']
        expires_in = tokens['expires_in']
        current_time = time.time()
        
        # 检查是否在过期时间前buffer_seconds秒
        return (current_time - saved_at) >= (expires_in - buffer_seconds)

class OAuthCallbackHandler(BaseHTTPRequestHandler):
    """OAuth回调处理器"""
    
    def do_GET(self):
        """处理GET请求，获取授权码"""
        try:
            # 解析URL参数
            url_parts = urllib.parse.urlparse(self.path)
            query_params = urllib.parse.parse_qs(url_parts.query)
            
            if 'code' in query_params:
                # 成功获取授权码
                self.server.auth_code = query_params['code'][0]
                self.server.state = query_params.get('state', [None])[0]
                
                # 返回成功页面
                self.send_response(200)
                self.send_header('Content-type', 'text/html; charset=utf-8')
                self.end_headers()
                
                success_html = """
                <!DOCTYPE html>
                <html>
                <head>
                    <meta charset="utf-8">
                    <title>授权成功</title>
                    <style>
                        body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
                        .success { color: #28a745; font-size: 24px; margin-bottom: 20px; }
                        .info { color: #6c757d; font-size: 16px; }
                    </style>
                </head>
                <body>
                    <div class="success">✅ 授权成功！</div>
                    <div class="info">您可以关闭此页面，程序将自动继续执行。</div>
                </body>
                </html>
                """
                self.wfile.write(success_html.encode('utf-8'))
                
            elif 'error' in query_params:
                # 授权失败
                error = query_params['error'][0]
                self.server.auth_error = error
                
                self.send_response(400)
                self.send_header('Content-type', 'text/html; charset=utf-8')
                self.end_headers()
                
                error_html = f"""
                <!DOCTYPE html>
                <html>
                <head>
                    <meta charset="utf-8">
                    <title>授权失败</title>
                    <style>
                        body {{ font-family: Arial, sans-serif; text-align: center; padding: 50px; }}
                        .error {{ color: #dc3545; font-size: 24px; margin-bottom: 20px; }}
                        .info {{ color: #6c757d; font-size: 16px; }}
                    </style>
                </head>
                <body>
                    <div class="error">❌ 授权失败</div>
                    <div class="info">错误信息: {error}</div>
                </body>
                </html>
                """
                self.wfile.write(error_html.encode('utf-8'))
                
        except Exception as e:
            logger.error(f"Error handling OAuth callback: {e}")
            self.send_response(500)
            self.end_headers()
    
    def log_message(self, format, *args):
        """禁用默认日志输出"""
        pass

class FeishuBitableClient:
    """飞书多维表格API客户端"""
    
    def __init__(self, client_id: str, client_secret: str, redirect_uri: str = "http://localhost:8080/callback"):
        self.client_id = client_id
        self.client_secret = client_secret
        self.redirect_uri = redirect_uri
        self.token_storage = TokenStorage()
        self.access_token = None
        self.refresh_token = None
        
        # API端点
        self.auth_url = "https://accounts.feishu.cn/open-apis/authen/v1/authorize"
        self.token_url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
        self.bitable_base_url = "https://open.feishu.cn/open-apis/bitable/v1"
        
        # 加载已保存的token
        self._load_saved_tokens()
    
    def _load_saved_tokens(self):
        """加载已保存的token"""
        tokens = self.token_storage.load_tokens()
        if tokens:
            self.access_token = tokens.get('access_token')
            self.refresh_token = tokens.get('refresh_token')
            
            # 检查token是否需要刷新
            if self.token_storage.is_token_expired(tokens):
                logger.info("Token is about to expire...")
                # 对于tenant_access_token，直接重新获取
                if tokens.get('token_type') == 'tenant_access_token':
                    logger.info("Tenant access token expired, will get new one when needed")
                    self.access_token = None
                elif self.refresh_token:
                    logger.info("Attempting to refresh user access token...")
                    self.refresh_access_token()
                else:
                    logger.warning("No refresh token available, need to re-authorize")
                    self.access_token = None
    
    def get_authorization_url(self, scope: str = "bitable:app:readonly offline_access", state: str = "RANDOMSTRING") -> str:
        """生成授权URL"""
        params = {
            'client_id': self.client_id,
            'redirect_uri': self.redirect_uri,
            'scope': scope,
            'state': state
        }
        
        query_string = urllib.parse.urlencode(params)
        return f"{self.auth_url}?{query_string}"
    
    def get_tenant_access_token(self) -> bool:
        """直接获取tenant_access_token（自建应用）"""
        try:
            data = {
                'app_id': self.client_id,
                'app_secret': self.client_secret
            }
            
            headers = {
                'Content-Type': 'application/json; charset=utf-8'
            }
            
            response = requests.post(self.token_url, json=data, headers=headers)
            response.raise_for_status()
            
            token_data = response.json()
            
            if token_data.get('code') == 0:
                # tenant_access_token API返回的字段名是tenant_access_token
                self.access_token = token_data['tenant_access_token']
                # tenant_access_token没有refresh_token
                self.refresh_token = None
                
                # 构造保存的token数据格式
                save_data = {
                    'access_token': self.access_token,
                    'tenant_access_token': self.access_token,
                    'token_type': 'tenant_access_token',
                    'expires_in': token_data.get('expire', 7200),  # 默认2小时
                    'created_at': int(time.time())
                }
                
                # 保存token到本地
                self.token_storage.save_tokens(save_data)
                
                logger.info("Tenant access token obtained successfully")
                return True
            else:
                logger.error(f"Tenant token request failed: {token_data}")
                return False
                
        except Exception as e:
            logger.error(f"Failed to get tenant access token: {e}")
            return False
    
    def start_oauth_flow(self, scope: str = "bitable:app:readonly offline_access", state: str = "RANDOMSTRING") -> bool:
        """启动OAuth授权流程（用户授权模式，已弃用，请使用get_tenant_access_token）"""
        logger.warning("OAuth flow is deprecated for internal apps. Use get_tenant_access_token() instead.")
        return self.get_tenant_access_token()
    
    def exchange_code_for_tokens(self, auth_code: str) -> bool:
        """获取tenant_access_token（自建应用）"""
        try:
            data = {
                'app_id': self.client_id,
                'app_secret': self.client_secret
            }
            
            headers = {
                'Content-Type': 'application/json; charset=utf-8'
            }
            
            response = requests.post(self.token_url, json=data, headers=headers)
            response.raise_for_status()
            
            token_data = response.json()
            
            if token_data.get('code') == 0:
                # tenant_access_token API返回的字段名是tenant_access_token
                self.access_token = token_data['tenant_access_token']
                # tenant_access_token没有refresh_token
                self.refresh_token = None
                
                # 构造保存的token数据格式
                save_data = {
                    'access_token': self.access_token,
                    'tenant_access_token': self.access_token,
                    'token_type': 'tenant_access_token',
                    'expires_in': token_data.get('expire', 7200),  # 默认2小时
                    'created_at': int(time.time())
                }
                
                # 保存token到本地
                self.token_storage.save_tokens(save_data)
                
                logger.info("Tenant access token obtained successfully")
                return True
            else:
                logger.error(f"Tenant token request failed: {token_data}")
                return False
                
        except Exception as e:
            logger.error(f"Failed to get tenant access token: {e}")
            return False
    
    def refresh_access_token(self) -> bool:
        """刷新访问令牌"""
        if not self.refresh_token:
            logger.error("No refresh token available")
            return False
        
        try:
            data = {
                'grant_type': 'refresh_token',
                'client_id': self.client_id,
                'client_secret': self.client_secret,
                'refresh_token': self.refresh_token
            }
            
            headers = {
                'Content-Type': 'application/json; charset=utf-8'
            }
            
            response = requests.post(self.token_url, json=data, headers=headers)
            response.raise_for_status()
            
            token_data = response.json()
            
            if token_data.get('code') == 0:
                self.access_token = token_data['access_token']
                self.refresh_token = token_data.get('refresh_token', self.refresh_token)
                
                # 保存新的token
                self.token_storage.save_tokens(token_data)
                
                logger.info("Tokens refreshed successfully")
                return True
            else:
                logger.error(f"Token refresh failed: {token_data}")
                return False
                
        except Exception as e:
            logger.error(f"Failed to refresh tokens: {e}")
            return False
    
    def ensure_valid_token(self) -> bool:
        """确保有有效的访问令牌"""
        if not self.access_token:
            logger.info("No access token, getting tenant access token...")
            return self.get_tenant_access_token()
        
        # 检查token是否需要刷新
        tokens = self.token_storage.load_tokens()
        if tokens and self.token_storage.is_token_expired(tokens):
            logger.info("Token is about to expire, getting new tenant access token...")
            # tenant_access_token没有refresh机制，直接重新获取
            return self.get_tenant_access_token()
        
        return True
    
    def _make_request(self, method: str, url: str, **kwargs) -> requests.Response:
        """发起API请求"""
        if not self.ensure_valid_token():
            raise Exception("Failed to obtain valid access token")
        
        headers = kwargs.get('headers', {})
        headers['Authorization'] = f'Bearer {self.access_token}'
        headers['Content-Type'] = 'application/json; charset=utf-8'
        kwargs['headers'] = headers
        
        response = requests.request(method, url, **kwargs)
        
        # 如果token过期，尝试刷新后重试
        if response.status_code == 401:
            logger.info("Token expired, attempting to refresh...")
            if self.refresh_access_token():
                headers['Authorization'] = f'Bearer {self.access_token}'
                response = requests.request(method, url, **kwargs)
        
        return response
    
    # 多维表格操作方法
    
    def get_records(self, app_token: str, table_id: str, record_ids: List[str]) -> Dict[str, Any]:
        """批量获取记录"""
        url = f"{self.bitable_base_url}/apps/{app_token}/tables/{table_id}/records/batch_get"
        
        data = {
            'record_ids': record_ids
        }
        
        response = self._make_request('POST', url, json=data)
        response.raise_for_status()
        
        return response.json()
    
    def create_record(self, app_token: str, table_id: str, fields: Dict[str, Any]) -> Dict[str, Any]:
        """创建记录"""
        url = f"{self.bitable_base_url}/apps/{app_token}/tables/{table_id}/records"
        
        data = {
            'fields': fields
        }
        
        response = self._make_request('POST', url, json=data)
        response.raise_for_status()
        
        return response.json()
    
    def update_record(self, app_token: str, table_id: str, record_id: str, fields: Dict[str, Any]) -> Dict[str, Any]:
        """更新记录"""
        url = f"{self.bitable_base_url}/apps/{app_token}/tables/{table_id}/records/{record_id}"
        
        data = {
            'fields': fields
        }
        
        response = self._make_request('PUT', url, json=data)
        response.raise_for_status()
        
        return response.json()
    
    def delete_record(self, app_token: str, table_id: str, record_id: str) -> Dict[str, Any]:
        """删除记录"""
        url = f"{self.bitable_base_url}/apps/{app_token}/tables/{table_id}/records/{record_id}"
        
        response = self._make_request('DELETE', url)
        response.raise_for_status()
        
        return response.json()
    
    def search_records(self, app_token: str, table_id: str, filter_condition: Optional[str] = None, 
                      sort: Optional[List[Dict[str, str]]] = None, page_size: int = 100) -> Dict[str, Any]:
        """搜索记录"""
        url = f"{self.bitable_base_url}/apps/{app_token}/tables/{table_id}/records/search"
        
        data = {
            'page_size': page_size
        }
        
        if filter_condition:
            data['filter'] = filter_condition
        
        if sort:
            data['sort'] = sort
        
        response = self._make_request('POST', url, json=data)
        response.raise_for_status()
        
        return response.json()
    
    # 字段操作方法
    
    def create_field(self, app_token: str, table_id: str, field_name: str, field_type: int, 
                    property: Optional[Dict[str, Any]] = None, description: Optional[Dict[str, Any]] = None,
                    ui_type: Optional[str] = None, client_token: Optional[str] = None) -> Dict[str, Any]:
        """创建字段
        
        Args:
            app_token: 多维表格App的唯一标识
            table_id: 数据表的唯一标识
            field_name: 字段名称
            field_type: 字段类型 (1:文本, 2:数字, 3:单选, 4:多选, 5:日期)
            property: 字段属性
            description: 字段描述
            ui_type: 字段在界面上的展示类型
            client_token: 操作的唯一标识，用于幂等操作
        """
        url = f"{self.bitable_base_url}/apps/{app_token}/tables/{table_id}/fields"
        
        data = {
            'field_name': field_name,
            'type': field_type
        }
        
        if property:
            data['property'] = property
        if description:
            data['description'] = description
        if ui_type:
            data['ui_type'] = ui_type
            
        params = {}
        if client_token:
            params['client_token'] = client_token
        
        response = self._make_request('POST', url, json=data, params=params)
        response.raise_for_status()
        
        return response.json()
    
    def delete_field(self, app_token: str, table_id: str, field_id: str) -> Dict[str, Any]:
        """删除字段
        
        Args:
            app_token: 多维表格App的唯一标识
            table_id: 数据表的唯一标识
            field_id: 字段的唯一标识
        """
        url = f"{self.bitable_base_url}/apps/{app_token}/tables/{table_id}/fields/{field_id}"
        
        response = self._make_request('DELETE', url)
        response.raise_for_status()
        
        return response.json()
    
    def list_fields(self, app_token: str, table_id: str, view_id: Optional[str] = None,
                   text_field_as_array: bool = False, page_token: Optional[str] = None,
                   page_size: int = 20) -> Dict[str, Any]:
        """列出字段
        
        Args:
            app_token: 多维表格App的唯一标识
            table_id: 数据表的唯一标识
            view_id: 视图的唯一标识
            text_field_as_array: 控制字段描述数据的返回格式
            page_token: 分页标记
            page_size: 分页大小
        """
        url = f"{self.bitable_base_url}/apps/{app_token}/tables/{table_id}/fields"
        
        params = {
            'page_size': page_size
        }
        
        if view_id:
            params['view_id'] = view_id
        if text_field_as_array:
            params['text_field_as_array'] = text_field_as_array
        if page_token:
            params['page_token'] = page_token
        
        response = self._make_request('GET', url, params=params)
        response.raise_for_status()
        
        return response.json()
    
    def update_field(self, app_token: str, table_id: str, field_id: str, field_name: str, 
                    field_type: int, property: Optional[Dict[str, Any]] = None,
                    description: Optional[Dict[str, Any]] = None, ui_type: Optional[str] = None) -> Dict[str, Any]:
        """更新字段
        
        Args:
            app_token: 多维表格App的唯一标识
            table_id: 数据表的唯一标识
            field_id: 字段的唯一标识
            field_name: 字段名称
            field_type: 字段类型 (1:文本, 2:数字, 3:单选, 4:多选, 5:日期)
            property: 字段属性
            description: 字段描述
            ui_type: 字段在界面上的展示类型
        """
        url = f"{self.bitable_base_url}/apps/{app_token}/tables/{table_id}/fields/{field_id}"
        
        data = {
            'field_name': field_name,
            'type': field_type
        }
        
        if property:
            data['property'] = property
        if description:
            data['description'] = description
        if ui_type:
            data['ui_type'] = ui_type
        
        response = self._make_request('PUT', url, json=data)
        response.raise_for_status()
        
        return response.json()

# 使用示例
if __name__ == "__main__":
    # 配置你的自建应用信息
    CLIENT_ID = "your_client_id_here"  # 替换为你的App ID
    CLIENT_SECRET = "your_client_secret_here"  # 替换为你的App Secret
    
    # 创建客户端（自建应用模式，使用tenant_access_token）
    client = FeishuBitableClient(CLIENT_ID, CLIENT_SECRET)
    
    # 确保有有效的访问令牌（会自动获取tenant_access_token）
    if client.ensure_valid_token():
        print("✅ 成功获取访问令牌")
        
        # 示例：搜索记录
        try:
            app_token = "your_app_token_here"  # 替换为你的多维表格app_token
            table_id = "your_table_id_here"    # 替换为你的数据表table_id
            
            # 搜索记录
            result = client.search_records(app_token, table_id, page_size=10)
            print(f"搜索结果: {json.dumps(result, indent=2, ensure_ascii=False)}")
            
        except Exception as e:
            print(f"❌ API调用失败: {e}")
    else:
        print("❌ 无法获取访问令牌")